import random
import cargo.serializers as serializers
from decimal import Decimal
from django.core.paginator import Paginator
from django.contrib import messages
from django.db.models import Q
from django.http import HttpResponse
from django.shortcuts import render, redirect, get_object_or_404
from django.views.generic import CreateView
from geopy.distance import geodesic
from .forms import CargoForm, CargoEditForm, CarModelForm
from .models import Cargo, Car, Location
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status


""" Веб-отображение """


def index(request):
    return render(request, 'base.html')  # Index


def locations(request):
    locations = Location.objects.all()
    spisok = len(locations)
    paginator = Paginator(locations, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    return render(request, 'location/locations.html', {'page_obj': page_obj, 'spisok': spisok})


def lenloc(request):
    locations = Location.objects.values_list('id', flat=True)
    print(len(locations))
    return HttpResponse(len(locations))


def cargos(request):
    """
    Выгрузка всех карточек груза.
    Для уменьшения количества запросов к базе данных используется пользовательский менеджер запросов nearby().
    Из выгруженных локаций выбираются все машины, находящиеся в них.
    """
    all_cargos = Cargo.objects.all()
    for cargo in all_cargos:
        cargo_location = (cargo.pick_up_location.latitude, cargo.pick_up_location.longitude)
        all_locatations = all_car_for_cargos(Decimal(cargo_location[0]), Decimal(cargo_location[1]), cargo_distance=450)
        cargo.cars = len(all_locatations)
    return render(request, 'cargo/cargos.html', {'cargos': all_cargos})  # Cargos


def cargo_new(request):
    form = CargoForm()
    if request.method == 'POST':
        form = CargoForm(request.POST)
        try:
            form.initial['pick_up_location'] = Location.objects.get(zipcode=int(request.POST['pick_up']))
            form.initial['deliver_location'] = Location.objects.get(zipcode=int(request.POST['deliver']))
        except Location.DoesNotExist:
            messages.error(request, 'неправильный зипкод')
            return render(request, 'cargo/cargo_new.html', {'form': form})
        if form.is_valid():
            cargo = Cargo.objects.create(
                pick_up_location=Location.objects.get(zipcode=int(request.POST['pick_up'])),
                deliver_location=Location.objects.get(zipcode=int(request.POST['deliver'])),
                cargo_weight=form.cleaned_data['cargo_weight'],
                description=form.cleaned_data['description'],
            )
            cargo.save()
            messages.success(request, 'Груз зарегистрирован')
            return redirect('cargos')
        else:
            messages.error(request, 'Не верные данные')
            return render(request, 'cargo/cargo_new.html', {'form': form})
    return render(request, 'cargo/cargo_new.html', {'form': form})


def all_car_for_cargos(lattitude, longitude, cargo_distance=0):
    """ Получение всех машин с расстоянием."""
    cargo_with_closest_vehicles = Car.objects.all()
    back_car = []
    if cargo_distance:
        for cargo in cargo_with_closest_vehicles:
            distance = geodesic((lattitude, longitude),
                                (cargo.current_location.latitude, cargo.current_location.longitude)).miles
            if cargo_distance >= distance:
                cargo.distance = Decimal(distance).quantize(Decimal('.01'))
                back_car.append(cargo)
    else:
        for cargo in cargo_with_closest_vehicles:
            distance = geodesic((lattitude, longitude),
                                (cargo.current_location.latitude, cargo.current_location.longitude)).miles
            cargo.distance = Decimal(distance).quantize(Decimal('.01'))
            back_car.append(cargo)
    return back_car


def cargo_view(request, pk):
    cargo = Cargo.objects.get(pk=pk)
    cars = all_car_for_cargos(cargo.pick_up_location.latitude, cargo.pick_up_location.longitude)
    return render(request, 'cargo/cargo_view.html', {'cargo': cargo, 'cars': cars})


def cargo_delete(request, pk):
    if request.method == 'POST':
        cargo = Cargo.objects.get(pk=pk)
        cargo.delete()
        messages.success(request, 'Груз удален')
        return redirect('cargos')


# вывод информации груза
class CargoView(CreateView):
    model = Cargo

    def get(self, request, *args, **kwargs):
        cargo = Cargo.objects.get(pk=kwargs['pk'])
        form = CargoEditForm(instance=cargo)
        return render(request, 'cargo/cargo_id.html', {'cargo': cargo,
                                                       'form': form})

    def post(self, request, *args, **kwargs):
        form = CarModelForm(request.POST, instance=self.get_object())
        if form.is_valid():
            car = Car.objects.get(pk=kwargs['pk'])
            try:
                location = Location.objects.get(zipcode=int(request.POST['zipcode']))
            except Location.DoesNotExist:
                messages.error(request, 'Не удалось найти такой zipcode')
                return redirect('CarView', pk=kwargs['pk'])
            car.current_location = location
            car.save()
            messages.success(request, 'Машина успешно изменена')
            return redirect('all_cars')
        return redirect('all_cars')


# вывод списка всех машин
def all_cars(request):
    cars = Car.objects.all()
    return render(request, 'car/cars.html', {'cars': cars})  # Cars


# просмотр карточки машины.
class CarView(CreateView):
    model = Car

    def get(self, request, *args, **kwargs):
        car = Car.objects.get(pk=kwargs['pk'])
        form = CarModelForm(instance=car)
        return render(request, 'car/car_id.html', {'car': car,
                                                   'form': form})

    def post(self, request, *args, **kwargs):
        form = CarModelForm(request.POST, instance=self.get_object())
        if form.is_valid():
            car = Car.objects.get(pk=kwargs['pk'])
            try:
                location = Location.objects.get(zipcode=int(request.POST['zipcode']))
            except Location.DoesNotExist:
                messages.error(request, 'Не удалось найти такой zipcode')
                return redirect('CarView', pk=kwargs['pk'])
            car.current_location = location
            car.save()
            messages.success(request, 'Машина успешно изменена')
            return redirect('all_cars')
        return redirect('all_cars')


# загрузка данных локации из csv файла для тестирования
def handle_uploaded_file(f):
    with open('save_csv_local.csv', 'wb+') as destination:
        for chunk in f.chunks():
            destination.write(chunk)
def locations_files_download(request):
    import csv
    if request.POST or request.FILES:
        uploaded_files = request.FILES['file']
        handle_uploaded_file(uploaded_files)
        with open('save_csv_local.csv', 'r') as f:
            writer = csv.reader(f, delimiter=',')
            for i, j in enumerate(writer):
                if i == 0:
                    continue
                if not Location.objects.filter(zipcode=int(j[0])):
                    location = Location.objects.create(
                        zipcode=int(j[0]),
                        latitude=Decimal(j[1]),
                        longitude=Decimal(j[2]),
                        city=j[3],
                        state=j[5],
                    )
                    location.save()
    return render(request, 'locations_files_download.html')


""" REST API """


class CarsApiView(APIView):
    """
    Get: Получение данных всех машин.

    Post: Добавление данные машины через post запрос.
        func:
            get_object_or_404(Location, zipcode=request.data['current_location']).id
            сделано для того, чтобы не выбрасывать исключение Location.DoesNotExist.(TODO)
    """
    def get(self, request):
        cars = Car.objects.all()
        if cars.exists():  # Проверяем, что есть данные
            serializer = serializers.CarSerializer(cars, many=True)
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response({"message": "No data available"}, status=status.HTTP_204_NO_CONTENT)

    def post(self, request, *args, **kwargs):
        modified_data = request.data.copy()
        if 'current_location' in request.data:
            location_id = get_object_or_404(Location, zipcode=request.data['current_location']).id
            modified_data['current_location'] = location_id
        serializer = serializers.CarSerializer(data=modified_data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class CarsIdApiView(APIView):
    """
    Get: Получение данных по id машины.
        Exception:
            Car.DoesNotExist: если машина не найдена
    Patch: Обновление данных машины.
        Exception:
            Car.DoesNotExist: если машина не найдена
            AttributeError: переданы не правильные атрибуты
            ValueError: переданы неверные данные

    """
    def get(self, request, pk):
        try:
            car = Car.objects.get(pk=pk)
            serializer = serializers.CarIdSerializer(car)
        except Car.DoesNotExist:
            return Response({"message": "Car does not exist"}, status=status.HTTP_404_NOT_FOUND)
        else:
            return Response(serializer.data, status=status.HTTP_200_OK)
        return Response({"message": "No data available"}, status=status.HTTP_204_NO_CONTENT)

    def patch(self, request, pk):
        try:
            car = Car.objects.get(pk=pk)
            serializer = serializers.CarPatchSerializer(car, data=request.data)
            if serializer.is_valid():
                serializer.save()
                return Response(serializer.data, status=status.HTTP_200_OK)
            return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        except Car.DoesNotExist:
            return Response({"message": "Car does not exist"}, status=status.HTTP_404_NOT_FOUND)
        except AttributeError:
            return Response({"message": "Attribute error"}, status=status.HTTP_400_BAD_REQUEST)  # TODO
        except TypeError:
            return Response({"message": "Type error"}, status=status.HTTP_400_BAD_REQUEST)  # TODO


class LocationApiView(APIView):
    """
    Get: Получение данные всех локаций.

    Post: Добавление локации через post запрос.
    """

    def get(self, request):
        locations = Location.objects.all()
        if locations.exists():  # Проверяем, что есть данные
            serializer = serializers.LocationSerializer(locations, many=True)
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response({"message": "No data available"}, status=status.HTTP_204_NO_CONTENT)

    def post(self, request, *args, **kwargs):
        serializer = serializers.LocationSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class CargoApiView(APIView):
    """
    Get: Получение данных всех грузов.
        func:
            all_car_for_cargos: Получение всех машин с расстоянием от груза до 450 миль.
        Exceptions:
            DoesNotExist: Объект не найден.

    Post: Добавление данных груза.
        Exceptions:
            ValueError: Неверный формат данных. Проверьте, что в поле 'pick_up_location' содержится число.
    """

    def get(self, request):
        cargos = Cargo.objects.all()
        query_params = request.query_params
        print(query_params)
        try:
            milles = int(query_params['milles'])
        except KeyError:
            milles = 450

        for key, value in query_params.items():
            if key == 'cargo_weight__lt':
                cargos = cargos.filter(Q(**{f'{key}': int(value)}))
            elif key == 'cargo_weight__lte':
                cargos = cargos.filter(Q(**{f'{key}': int(value)}))
            elif key == 'cargo_weight__gt':
                cargos = cargos.filter(Q(**{f'{key}': int(value)}))
            elif key == 'cargo_weight__gte':
                cargos = cargos.filter(Q(**{f'{key}': int(value)})) # TODO
        for cargo in cargos:
            cargo.count_car = len(all_car_for_cargos(Decimal(cargo.pick_up_location.latitude),
                                                     Decimal(cargo.pick_up_location.longitude),
                                                     cargo_distance=int(milles)))

        if cargos.exists():
            serializer = serializers.CargoGetSerializer(cargos, many=True)
            return Response(serializer.data, status=status.HTTP_200_OK)
        else:
            return Response({"message": "No data available"}, status=status.HTTP_204_NO_CONTENT)

    def post(self, request, *args, **kwargs):
        try:
            modified_data = request.data.copy()
            modified_data['pick_up_location'] = Location.objects.get(zipcode=int(request.data['pick_up_location'])).id
            modified_data['deliver_location'] = Location.objects.get(zipcode=int(request.data['deliver_location'])).id
            serializer = serializers.CargoCreateSerializer(data=modified_data)
        except ValueError as e:
            return Response({"message": str(e)}, status=status.HTTP_400_BAD_REQUEST)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class CargosIdApiView(APIView):
    """
    Get: Получение данных груза по id.
        func:
            all_car_for_cargos: Получение всех машин с расстоянием от груза.
        Exceptions:
            DoesNotExist: Объект не найден.

    Patch: Обновление данных груза по id.
        Exceptions:
            DoesNotExist: Объект не найден.

    Delete: Удаление данных груза по id.
        Exceptions:
            DoesNotExist: Объект не найден.
    """

    def get(self, request, pk):
        try:
            cargo = Cargo.objects.get(pk=pk)
            cargo.cars = all_car_for_cargos(Decimal(cargo.pick_up_location.latitude), Decimal(cargo.pick_up_location.longitude))
        except Cargo.DoesNotExist:
            return Response({"message": "Cargo does not exist"}, status=status.HTTP_404_NOT_FOUND)
        else:
            serializer = serializers.CargoGetIdSerializer(cargo)
            return Response(serializer.data, status=status.HTTP_200_OK)

    def patch(self, request, pk):
        try:
            cargo = Cargo.objects.get(id=pk)
        except Cargo.DoesNotExist:
            return Response({"message": "Cargo does not exist"}, status=status.HTTP_404_NOT_FOUND)

        serializer = serializers.CargoPatchSerializer(cargo, data=request.data, partial=True)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk):
        try:
            cargo = Cargo.objects.get(id=pk)
        except Cargo.DoesNotExist:
            return Response({"message": "Cargo does not exist"}, status=status.HTTP_404_NOT_FOUND)
        cargo.delete()
        return Response({"message": "Cargo deleted"}, status=status.HTTP_200_OK)


def randomize_car_location(cars):
    """
    Рандомизирует поле `current_location` переданного списка автомобилей.
    Args:
        cars: Список объектов модели `Car`.
    """
    locations = Location.objects.all()
    for car in cars:
        random_location = random.choice(locations)
        car.current_location = random_location
        car.save()
    return "Рандомизация прошла успешно"


def reupdate(request):
    """
    Обновляет поле `current_location` всех автомобилей.
    func:
        randomize_car_location(cars): Рандомизирует поле `current_location` переданного списка автомобилей.
    Exceptions:
        except: 400
    """

    try:
        car = Car.objects.all()
        randomize_car_location(car)
        return HttpResponse('Ok, code:200')
    except:
        return HttpResponse('Ok, code:500')


